// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef _FUNKY_LOOP_TRACK_HH
# define _FUNKY_LOOP_TRACK_HH

# include <deque>
# include <vector>
# include "signal.hh"

namespace funky {

  template <typename Signal, typename Control>
  struct loop_track_sig {
  private:
    typedef typename signal_traits<Signal>::sample sample;
    std::deque<std::vector<sample> > queue;
    std::vector<sample> recording;
    Signal signal;
    Control control;
    size_t position;
    std::bitset<3> flags;

  public:
    template <typename S, typename C>
    loop_track_sig(S&& s, C&& c)
      : signal(std::forward<S>(s)),
	control(std::forward<C>(c)),
	position(0) {
    }

    loop_track_sig(const loop_track_sig&) = default;

    loop_track_sig(loop_track_sig&& other):
      queue(std::move(other.queue)),
      recording(std::move(other.recording)),
      signal(std::move(other.signal)),
      control(std::move(other.control)),
      position(other.position),
      flags(std::move(other.flags)) {
    }

    loop_track_sig& operator=(const loop_track_sig& other) {
      return *this = loop_track_sig(other);
    }

    loop_track_sig& operator=(loop_track_sig&& other) {
      std::swap(queue, other.queue);
      std::swap(recording, other.recording);
      std::swap(signal, other.signal);
      std::swap(control, other.control);
      std::swap(position, other.position);
      std::swap(flags, other.flags);
      return *this;
    }

    const sample& operator*() const {
      static sample nosound;
      if (flags[1]) {
	if (position >= queue.front().size())
	  return nosound;
	return queue.front()[position];
      } else {
	return nosound;
      }
    }

    loop_track_sig& operator++() {
      ++signal;
      ++control;
      flags = *control;
      ++position;
      if (flags[2]) {
	if (!recording.empty()) {
	  queue.push_front(recording);
	}
	position = 0;
      }
      if (flags[0]) {
	if (position == recording.size()) {
	  recording.push_back(*signal);
	}
	else {
	  if (position >= recording.size()) {
	    recording.resize(position+1);
	  }
	  recording[position] += *signal;
	}
      }
      return *this;
    }

    loop_track_sig& operator++(int) {
      loop_track_sig ret{*this};
      ++*this;
      return ret;
    }
  };

  template <typename Signal, typename Control>
  struct signal_traits<loop_track_sig<Signal, Control> > {
    typedef size_t duration;
    typedef typename signal_traits<Signal>::sample sample;
  };

  template <typename S, typename C,
	    typename Signal = typename std::decay<S>::type,
	    typename Control = typename std::decay<C>::type>
  loop_track_sig<Signal, Control>
  loop_track(S&& s, C&& c) {
    return loop_track_sig<Signal, Control>{
      std::forward<S>(s), std::forward<C>(c)
    };
  }

}

#endif
